import { TokenService, Request, OpenedResourceId, InputUpdateRequest, ClosedResourceId, Command } from '../protocol/token.service';
import { customAlphabet } from 'nanoid';

export interface ResourceService {
    open(): OpenedResourceId;
    close(): Promise<any>;
    handleRequest(req: Array<Request>): Array<Command>;
    getCandidateTokens(req: InputUpdateRequest): Promise<Array<string>>;
}

export interface ResourceServiceFactory {
    (resourceId: string): ResourceService;
}

const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%&-;@', 12);
export class ResourceServiceManager implements TokenService {
    private serviceFactory: ResourceServiceFactory;
    private resources: Map<string, ResourceService>;
    private log = true;

    constructor(serviceFactory: ResourceServiceFactory) {
        this.serviceFactory = serviceFactory;
        this.resources = new Map();
    }

    async open(resourceId?: string): Promise<OpenedResourceId> {
        const id = resourceId || nanoid();
        let service = this.resources.get(id);
        if (!service) {
            service = this.serviceFactory(id);
            this.resources.set(id, service);
        }
        const opened = service.open();
        if (this.log) {
            console.log(`opened_resource = ${JSON.stringify(opened, null, 2)}`);
        }
        return opened;
    }

    async close(resourceId: string): Promise<ClosedResourceId> {
        const service = this.resources.get(resourceId);
        if (!service) {
            throw new Error(`Resource with id ${resourceId} not found`);
        }
        this.resources.delete(resourceId);
        await service.close();
        return { command: 'closed', resourceId };
    }

    async send(resourceId: string, req: Array<Request>): Promise<Array<Command>> {
        if (this.log) {
            console.log(`req = ${JSON.stringify(req, null, 2)}`);
        }
        const service = this.resources.get(resourceId);
        if (!service) {
            throw new Error(`Resource with id ${resourceId} not found`);
        }
        const result = service.handleRequest(req);
        if (this.log) {
            console.log(`resp = ${JSON.stringify(result, null, 2)}`);
        }
        return result;
    }

    async getCandidateTokens(resourceId: string, req: InputUpdateRequest): Promise<Array<string>> {
        const service = this.resources.get(resourceId);
        if (!service) {
            throw new Error(`Resource with id ${resourceId} not found`);
        }
        return service.getCandidateTokens(req);
    }
}

export default ResourceServiceManager;
